package com.example.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import com.example.train.business.domain.SkToken;
import com.example.train.business.domain.SkTokenExample;
import com.example.train.business.enums.RedisKeyPreEnum;
import com.example.train.business.mapper.SkTokenMapper;
import com.example.train.business.mapper.cust.SkTokenMapperCust;
import com.example.train.business.req.SkTokenQueryReq;
import com.example.train.business.req.SkTokenSaveReq;
import com.example.train.business.resp.SkTokenQueryResp;
import com.example.train.common.resp.PageResp;
import com.example.train.common.util.SnowUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SkTokenService {

    private static final Logger LOG = LoggerFactory.getLogger(SkTokenService.class);

    @Resource
    private SkTokenMapper skTokenMapper;

    @Resource
    private DailyTrainSeatService dailyTrainSeatService;

    @Resource
    private DailyTrainStationService dailyTrainStationService;

    @Resource
    private SkTokenMapperCust skTokenMapperCust;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Value("${spring.profiles.active}")
    private String env;

    /**
     * 添加乘车人及修改
     * @param req
     * @return void
     **/
    public void save(SkTokenSaveReq req){
        DateTime now = DateTime.now();
        SkToken skToken= BeanUtil.copyProperties(req,SkToken.class);
        //若ID为空说明是第一次添加
        if(ObjUtil.isNull(skToken.getId())){

            skToken.setId(SnowUtil.getSnowflakeNextId());
            skToken.setCreateTime(now);
            skToken.setUpdateTime(now);
            skTokenMapper.insert(skToken);
        }else{
            skToken.setUpdateTime(now);
            skTokenMapper.updateByPrimaryKey(skToken);
        }
    }

    public PageResp<SkTokenQueryResp> queryList(SkTokenQueryReq req){
        SkTokenExample skTokenExample=new SkTokenExample();
        skTokenExample.setOrderByClause("id desc");
        SkTokenExample.Criteria criteria= skTokenExample.createCriteria();

        LOG.info("查询页码：{}", req.getPage());
        LOG.info("每页条数：{}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<SkToken> skTokenList=skTokenMapper.selectByExample(skTokenExample);
        //获取分页查询的skTokenList分页数和总记录数
        PageInfo<SkToken> pageInfo=new PageInfo<>(skTokenList);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<SkTokenQueryResp> list=BeanUtil.copyToList(skTokenList, SkTokenQueryResp.class);

        PageResp<SkTokenQueryResp> pageResp=new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    public void delete(Long id){
        skTokenMapper.deleteByPrimaryKey(id);
    }

    /**
     * 初始化
     */
    public void genDaily(Date date, String trainCode){
        LOG.info("删除日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
        SkTokenExample skTokenExample=new SkTokenExample();
        skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
        skTokenMapper.deleteByExample(skTokenExample);

        DateTime now = DateTime.now();
        SkToken skToken = new SkToken();
        skToken.setDate(date);
        skToken.setTrainCode(trainCode);
        skToken.setCreateTime(now);
        skToken.setUpdateTime(now);
        skToken.setId(SnowUtil.getSnowflakeNextId());

        int seatCount = dailyTrainSeatService.countSeat(date, trainCode);
        LOG.info("车次【{}】座位数：{}", trainCode, seatCount);

        long stationCount = dailyTrainStationService.countByTrainCode(date, trainCode);
        LOG.info("车次【{}】到站数：{}", trainCode, stationCount);

        // 一趟火车最多可以卖（seatCount * stationCount * 比例）张火车票，此处假设为0.75，实际可根据历史数据估算
        int count=(int)(stationCount*seatCount*0.75);
        LOG.info("车次【{}】初始生成令牌数：{}", trainCode, count);
        skToken.setCount(count);

        skTokenMapper.insert(skToken);
    }

    /**
     * 校验令牌
     */
    public boolean validSkToken(Date date, String trainCode, Long memberId) {
        LOG.info("会员【{}】获取日期【{}】车次【{}】的令牌开始", memberId, DateUtil.formatDate(date), trainCode);
        //开发环境方便压测不获取令牌锁
        if(!env.equals("dev")){
            // 先获取令牌锁，再校验令牌余量，防止机器人抢票
            RLock lock = null;
            String lockKey = RedisKeyPreEnum.SK_TOKEN + "-" + DateUtil.formatDate(date) + "-" + trainCode + "-" + memberId;
            lock = redissonClient.getLock(lockKey);
            try {
                boolean tryLock = lock.tryLock(0, 5, TimeUnit.SECONDS);
                if (tryLock) {
                    LOG.info("恭喜，抢到令牌锁了！lockKey：{}", lockKey);
                } else {
                    LOG.error("获取令牌锁失败：{}", lockKey);
                    return false;
                }
            } catch (InterruptedException e) {
                LOG.error("获取令牌锁异常");
            }
        }

        String skTokenCountKey = RedisKeyPreEnum.SK_TOKEN_COUNT + "-" + DateUtil.formatDate(date) + "-" + trainCode;
        Object skTokenCount = stringRedisTemplate.opsForValue().get(skTokenCountKey);
        if (skTokenCount != null) {
            LOG.info("缓存中有该车次令牌大闸的key：{}", skTokenCountKey);
            Long count = stringRedisTemplate.opsForValue().decrement(skTokenCountKey, 1);
            if (count < 0L) {
                LOG.error("获取令牌失败：{}", skTokenCountKey);
                return false;
            }else{
                LOG.info("获取令牌后，令牌余数：{}", count);
                //更新过期时间
                stringRedisTemplate.expire(skTokenCountKey,60,TimeUnit.SECONDS);
                // 每获取5个令牌更新一次数据库，减少MySQL读写压力
                // 该扣减方式会导致令牌初始不为5的倍数时额外扣减令牌，但对实际影响不大
                if(count%5==0){
                    skTokenMapperCust.decrease(date,trainCode,5);
                }
                return true;
            }
        }else{
            LOG.info("缓存中没有该车次令牌大闸的key：{}", skTokenCountKey);
            // 检查是否还有令牌
            SkTokenExample skTokenExample=new SkTokenExample();
            skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
            List<SkToken> tokenCountList=skTokenMapper.selectByExample(skTokenExample);
            if(CollUtil.isEmpty(tokenCountList)){
                LOG.info("找不到日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
                return false;
            }
            SkToken skToken= tokenCountList.get(0);
            if(skToken.getCount()<=0){
                LOG.info("日期【{}】车次【{}】的令牌余量为0", DateUtil.formatDate(date), trainCode);
                return false;
            }
            // 令牌还有余量,则数量-1
            Integer count = skToken.getCount() - 1;
            skToken.setCount(count);
            LOG.info("将该车次令牌大闸放入缓存中，key: {}， count: {}", skTokenCountKey, count);
            stringRedisTemplate.opsForValue().set(skTokenCountKey,String.valueOf(count),60,TimeUnit.SECONDS);
            skTokenMapper.updateByPrimaryKey(skToken);
            return true;
        }


        // 令牌约等于库存，令牌没有了，就不再卖票，不需要再进入购票主流程去判断库存，增加效率
//        int updateCount=skTokenMapperCust.decrease(date,trainCode);
//        if(updateCount>0){
//            return true;
//        }else{
//            return false;
//        }
    }
}
